/**
 * 
 */
package parsetree;


import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;

import gui.text.MathDocument;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ExecutionException;

import javax.swing.event.DocumentEvent.EventType;
import javax.swing.text.AbstractDocument.DefaultDocumentEvent;
import javax.swing.text.BadLocationException;

import libraries.Library;
import metagrammar.Operator;
import metagrammar.Type;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import parsetree.AbstractBranchElement;
import parsetree.AbstractElement;
import parsetree.DefaultOperatorElement;
import parsetree.Element;
import parsetree.Insert;
import parsetree.LineElement;
import parsetree.OperatorSpec;
import parsetree.ParseTree;
import parsetree.ParsingElement;
import parsetree.Removal;
import parsetree.RootElement;
import parsetree.exceptions.DocumentInitException;
import util.TrieMap;

/**
 * @author Joshua Lockerman
 *
 */
public class ParseTreeTest {
    
    private final Operator mockOperator = new Operator(Type.UNTYPED);
    private final OperatorSpec mockSpec = new OperatorSpec(mockOperator, "a", 0);
    
    private final TestLibrary testLib = new TestLibrary();
    
    private MathDocument document;
    private ParseTree tree;
    private RootElement root;
    
    /**
     * @throws java.lang.Exception
     */
    @Before
    public void setUp() throws Exception
    {
        document = new MathDocument(testLib);
        TrieMap<OperatorSpec> mockMap = new TrieMap<OperatorSpec>();
        tree = new ParseTree(document, testLib);
        root = new RootElement(tree);
    }
    
    /**
     * @throws java.lang.Exception
     */
    @After
    public void tearDown() throws Exception
    {
    }
    
    @Test
    public void testConstructer() throws ExecutionException
    {
        tree = new ParseTree(document, testLib);
        assertNotNull(tree);
    }
    
    @Test
    public void testInvaidConstructer() throws ExecutionException
    {
        try {
            ParseTree invalidTree = new ParseTree(null, null);
            fail();
        }
        catch(DocumentInitException e) {
            //sucess
        }
    }
    
    @Test
    public void testInsertIntoDocumentWithValidInput() throws BadLocationException
    {
        document.insertString(0, "aaaa", null);
        assertEquals(0, root.getStartOffset());
        assertEquals(5, root.getEndOffset());
    }
    
    @Test
    public void testInsertIntoDocumentWithInvalidInput()
    {
        try {
            document.insertString(2, "aaaa", null);
            fail();
        }
        catch(BadLocationException e) {
            //success
        }
    }
    
    @Test
    public void testParsingElementSplit() throws BadLocationException
    {
        document.insertString(0, "aaaa", null);
        AbstractElement parsingElement = new ParsingElement(root, 0, 5);
        Element[] elements = parsingElement.split(2);
        assertEquals(0, elements[0].getStartOffset());
        assertEquals(2, elements[0].getEndOffset());
        assertEquals(2, elements[1].getStartOffset());
        assertEquals(5, elements[1].getEndOffset());
    }
    
    @Test
    public void testInvalidParisngElementSplit()
    {
        try {
            document.insertString(0, "aaaa", null);
            AbstractElement parsingElement = new ParsingElement(root, 0, 5);
            parsingElement.split(6);
            fail();
        }
        catch(BadLocationException e) {
            //sucess
        }
        
        try {
            AbstractElement parsingElement = new ParsingElement(root, 0, 5);
            parsingElement.split(0);
            fail();
        }
        catch(BadLocationException e) {
            //sucess
        }
        
        try {
            AbstractElement parsingElement = new ParsingElement(root, 0, 5);
            parsingElement.split(-1);
            fail();
        }
        catch(BadLocationException e) {
            //sucess
        }
    }
    
    @Test
    public void testAbstractAddChild() throws BadLocationException
    {
        document.insertString(0, "aaaa", null);
        AbstractBranchElement line = (AbstractBranchElement) root.getChildContaining(1);
        AbstractElement parsingElement = new ParsingElement(line, 1, 2);
        line.addChild(parsingElement);
        assertEquals(2, line.getChildCount());
        assertEquals(0, line.getStartOffset());
        assertEquals(5, line.getEndOffset());
    }
    
    @Test
    public void lineElementSplitWithChildAtSplit() throws BadLocationException
    {
        document.insertString(0, "aaaa", null);
        Element line = root.getChildContaining(1);
        Element[] elements = line.split(2);
        assertEquals(0, elements[0].getStartOffset());
        assertEquals(2, elements[0].getEndOffset());
        assertEquals(2, elements[1].getStartOffset());
        assertEquals(5, elements[1].getEndOffset());
    }
    
    @Test
    public void lineElementSplitWithOutChildAtSplit() throws BadLocationException
    {
        document.insertString(0, "aaaa", null);
        LineElement line = new LineElement(root);
        line.addChild(new ParsingElement(line, 0, 2));
        line.addChild(new ParsingElement(line, 2, 5));
        Element[] elements = line.split(2);
        assertEquals(0, elements[0].getStartOffset());
        assertEquals(2, elements[0].getEndOffset());
        assertEquals(2, elements[1].getStartOffset());
        assertEquals(5, elements[1].getEndOffset());
    }
    
    @Test
    public void lineElementNewLineCharAdded() throws BadLocationException
    {
        document.insertString(0, "\n", null);
        DefaultDocumentEvent change = document.new DefaultDocumentEvent(0, 1, EventType.INSERT);
        root.insertUpdate(new FowardingChange(change), null);
        assertEquals(2, root.getChildCount());
    }
    
    @Test
    public void lineElementNewLineCharsAdded() throws BadLocationException
    {
        try {
            document.insertString(0, "\n\n", null);
        }
        catch(Exception e) {
        }
        DefaultDocumentEvent change = document.new DefaultDocumentEvent(0, 2, EventType.INSERT);
        root.insertUpdate(new FowardingChange(change), null);
        assertEquals(3, root.getChildCount());
    }
    
    @Test
    public void lineElementNewLineCharsAddedIteratively() throws BadLocationException
    {
        for(int i = 0; i < 5; i++) {
            document.insertString(i, "\n", null);
            DefaultDocumentEvent change = document.new DefaultDocumentEvent(i, 1, EventType.INSERT);
            root.insertUpdate(new FowardingChange(change), null);
        }
        assertEquals(6, root.getChildCount());
    }
    
    @Test
    public void getStartsEndsWith() throws BadLocationException
    {
        assertEquals(root.getChildEndsWith(1), root.getChildStartsWith(0));
    }
    
    @Test
    public void constructOperatorElement() throws BadLocationException
    {
        document.insertString(0, "aaaa", null);
        OperatorSpec spec = new OperatorSpec(mockOperator, "", 0);
        DefaultOperatorElement operatorElement = new DefaultOperatorElement(root, spec, 0, 1);
        assertNotNull(operatorElement);
        assertNotNull(operatorElement.getChildStartsWith(0));
    }
    
    @Test
    public void splitBeforeOperator() throws BadLocationException
    {
        document.insertString(0, "aaaa", null);
        LineElement line = (LineElement) root.getChildContaining(1);
        Operator testOp = new Operator(Type.ROOT, new Type[] { Type.UNTYPED, Type.UNTYPED });
        OperatorSpec spec = new OperatorSpec(testOp, "a", 2);
        DefaultOperatorElement opElement = new DefaultOperatorElement(line, spec, 2, 3);
        DefaultOperatorElement leaf = new DefaultOperatorElement(opElement, mockSpec, 0, 2);
        opElement.addChild(leaf);
        line.addChild(opElement);
        Element[] split = opElement.split(1);
        assertEquals(3, split.length);
    }
    
    @Test
    public void splitAtOperator() throws BadLocationException
    {
        document.insertString(0, "aaaa", null);
        LineElement line = (LineElement) root.getChildContaining(1);
        Operator testOp = new Operator(Type.ROOT, new Type[] { Type.UNTYPED, Type.UNTYPED });
        OperatorSpec spec = new OperatorSpec(testOp, "a", 1);
        DefaultOperatorElement opElement = new DefaultOperatorElement(line, spec, 1,3);
        DefaultOperatorElement leaf = new DefaultOperatorElement(opElement, mockSpec, 0, 1);
        opElement.addChild(leaf);
        leaf = new DefaultOperatorElement(opElement, mockSpec, 3, 4);
        opElement.addChild(leaf);
        line.addChild(opElement);
        Element[] split = opElement.split(2);
        assertEquals(4, split.length);
    }
    
    @Test
    public void splitAfterOperator() throws BadLocationException
    {
        document.insertString(0, "aaaa", null);
        LineElement line = (LineElement) root.getChildContaining(1);
        Operator testOp = new Operator(Type.ROOT, new Type[] { Type.UNTYPED, Type.UNTYPED });
        OperatorSpec spec = new OperatorSpec(testOp, "a", 1);
        DefaultOperatorElement opElement = new DefaultOperatorElement(line, spec, 1, 4);
        DefaultOperatorElement leaf = new DefaultOperatorElement(opElement, mockSpec, 0, 1);
        opElement.addChild(leaf);
        leaf = new DefaultOperatorElement(opElement, mockSpec, 2, 4);
        opElement.addChild(leaf);
        line.addChild(opElement);
        Element[] split = opElement.split(3);
        assertEquals(3, split.length);
    }
    
    private static class TestLibrary extends Library {
        
        private final ArrayList<Operator> operators;
        
        public TestLibrary()
        {
            super("test", Collections.<Type> emptyList(), Collections.<Operator> emptyList());
            this.operators = new ArrayList<Operator>();
        }
        
        @Override
        public Collection<Operator> loadOperators() throws ExecutionException
        {
            return operators;
        }
        
    }
    
    private static class FowardingChange implements Insert, Removal {
        
        private final DefaultDocumentEvent change;
        
        public FowardingChange(DefaultDocumentEvent chng)
        {
            change = chng;
        }
        
        @Override
        public int getOffset()
        {
            return change.getOffset();
        }
        
        @Override
        public int getLength()
        {
            return change.getLength();
        }
        
    }
}
